/*
    The NeonX Library

    Copyright (c) 2013 darkcat

    Code covered by the MIT License
*/

#pragma once

// nx...
#include "config/general.h"
// nx::end
#include "algorithm/foreach.h"
// nx_typeof
#include "type/typeof.h"
// nx_traits
#include "type/typetraits.h"
// nx_assert
#include "bugfix/assert.h"
// Stream
#include "kernel/stream.h"

//////////////////////////////////////////////////////////////////////////
NX_BEG
//////////////////////////////////////////////////////////////////////////

/*
    push
*/

namespace private_
{
    template
    <
        typename T, 
        bool IsArray_ = nx_is_array(T), 
        bool IsPod_   = nx_is_pod(T)
    >
    struct Push_;

    template <typename T, bool IsPod_>
    struct Push_<T, true, IsPod_>
    {
        template <typename V>
        static void _(const V& val, typename TraitsContainer<T>::ite_t ite, T& /*set*/)
        { (*ite) = val; }
    };

    template <typename T>
    struct Push_<T, false, true>
    {
        template <typename V>
        static void _(const V& val, typename TraitsContainer<T>::ite_t /*ite*/, T& set)
        { set += val; }
    };

    template <typename T>
    struct Push_<T, false, false>
    {
        template <typename V>
        static void _(const V& val, typename TraitsContainer<T>::ite_t ite, T& set)
        { set.insert(ite, val); }
    };
}

template <typename T>
struct Push : private_::Push_<T>
{};

/*
    pop
*/

namespace private_
{
    template
    <
        typename T, 
        bool IsArray_ = nx_is_array(T), 
        bool IsPod_   = nx_is_pod(T)
    >
    struct Pop_;

    template <typename T, bool IsPod_>
    struct Pop_<T, true, IsPod_>
    {
        static void _(typename TraitsContainer<T>::ite_t /*ite*/, T& /*set*/)
        {}
    };

    template <typename T>
    struct Pop_<T, false, true>
    {
        static void _(typename TraitsContainer<T>::ite_t ite, T& set)
        { set -= ite; }
    };

    template <typename T>
    struct Pop_<T, false, false>
    {
        static void _(typename TraitsContainer<T>::ite_t ite, T& set)
        { set.erase(ite); }
    };
}

template <typename T>
struct Pop : private_::Pop_<T>
{};

/*
    assign
*/

template <typename T, template <typename> class P>
struct Assign;

template <typename T>
struct Assign<T, Push>
{
    struct P_stream_
    {
        T* set_p;
        template <typename V> void _(const V& val)
        {
            nx_assert(set_p);
            Push<T>::_(val, end(*set_p), *set_p);
        }
    };

    class Stream_ : public Stream<P_stream_>
    {
    public:
        const Stream_& operator()(T* p)
        {
            Stream<P_stream_>::set_p = p;
            return (*this);
        }

        template <typename K, typename V>
        const Stream_& operator()(const K& k, const V& v) const
        {
            (*this) << typename TraitsContainer<T>::pair_t(k, v);
            return (*this);
        }
    };

    typedef Stream_ stream_t;
};

template <typename T, size_t N>
struct Assign<T[N], Push>
{
    struct P_stream_
    {
        T(* set_p)[N];
        typename TraitsContainer<T[N]>::ite_t iter;

        template <typename V> void _(const V& val)
        {
            nx_assert(set_p && iter);
            Push<T[N]>::_(val, iter++, *set_p);
        }
    };

    class Stream_ : public Stream<P_stream_>
    {
    public:
        const Stream_& operator()(T(* p)[N])
        {
            Stream<P_stream_>::set_p = p;
            Stream<P_stream_>::iter  = begin(*p);
            return (*this);
        }

        template <typename K, typename V>
        const Stream_& operator()(const K& k, const V& v) const
        {
            (*this) << typename nx_traits(T[N])::pair_t(k, v);
            return (*this);
        }
    };

    typedef Stream_ stream_t;
};

template <typename T>
struct Assign<T, Pop>
{
    struct P_stream_
    {
        T* set_p;
        P_stream_(T* p) : set_p(p) {}
        template <typename V> void _(V& ite)
        {
            nx_assert(set_p);
            Pop<T>::_(ite, *set_p);
        }
    };

    class Stream_ : public Stream<P_stream_>
    {
    public:
        const Stream_& operator()(T* p)
        {
            Stream<P_stream_>::set_p = p;
            return (*this);
        }
    };

    typedef Stream_ stream_t;
};

/*
    push/pop macros
*/

#define nx_push(set, ...) \
    nx::Assign<nx_typeof(set), nx::Push>::stream_t()(&set),##__VA_ARGS__

#define nx_pop(set, ...) \
    nx::Assign<nx_typeof(set), nx::Pop>::stream_t()(&set),##__VA_ARGS__

//////////////////////////////////////////////////////////////////////////
NX_END
//////////////////////////////////////////////////////////////////////////
